﻿using System.Collections.Generic;
using System.Linq;

namespace RNA_Libraries
{
  public class Graph : HashSet<Vertex>
  {
    public Graph() { }

    public Graph(IEnumerable<Sequence> reads)
    {
      foreach (var sequence in reads)
      {
        Add(new Vertex(sequence));
      }
    }

    public new void Add(Vertex key)
    {
      if (Contains(key))
      {
        var tmp = new Vertex(key + key);
        this.Remove(this.First(k => k == key));
        this.Add(tmp);
      }
      else
      {
        base.Add(key);
      }
    }

    public List<Vertex> Run(Sequence refSeq)
    {
      m_minReadLength = this.Min(seq => seq.Length);

      int minLen = -1;
      int lev = int.MaxValue;
      int l1 = int.MaxValue;
      do
      {
        this.RemoveWhere(v => v.Length == 0);
        RecalculateEdges((int)(this.Min(seq => seq.Length) * 1.5));
        Edge selected;
        int minlev;
        SelectEdge(refSeq, ref lev, ref l1, out selected, out minlev);
        if (minlev > l1) break;
        if (selected != null)
        {
          this.Remove(selected.Start);
          this.Remove(selected.End);
          this.Add(new Vertex(selected.Start + selected.End));
        }
        lev = minlev;
      }
      while (lev <= l1);
      return this.ToList<Vertex>();
    }

    private void SelectEdge(Sequence refSeq, ref int lev, ref int l1, out Edge selected, out int minlev)
    {
      selected = null;
      minlev = int.MaxValue;
      int maxL = m_edges.Max(e => e.L);
      foreach (var e in m_edges.Where(e => e.L == maxL))
      {
        lev = SequenceProcessing.LevenshteinDistance(refSeq, e.PullEdge());
        var l11 = System.Math.Min(SequenceProcessing.LevenshteinDistance(refSeq, e.Start), SequenceProcessing.LevenshteinDistance(refSeq, e.End));
        if (lev < minlev)
        {
          minlev = lev;
          l1 = l11;
          selected = e;
        }
      }
    }

    private void RecalculateEdges(int l)
    {
      m_edges.Clear();
      var selectedVertexes = this.Where(v => v.Length <= l);
      foreach (var v in selectedVertexes)
      {
        foreach (var t in selectedVertexes.Where(s => s != v))
        {
          m_edges.Add(new Edge(v, t));
        }
      }
    }

    public List<Vertex> Result { get { return m_result; } }

    #region Implementation

    private static int m_minReadLength = int.MaxValue;
    private List<Edge> m_edges = new List<Edge>(100);
    private List<Vertex> m_result = new List<Vertex>(100);

    #endregion
  }
}
